package com.imooc.activitiweb.controller;

import com.imooc.activitiweb.util.AjaxResponse;
import com.imooc.activitiweb.util.GlobalConfig;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.InputStream;
import java.util.*;
import java.util.zip.ZipInputStream;

/**
 * 流程定义 控制类
 */
@RestController
@RequestMapping("/processDefinition")
public class ProcessDefinitionController {

    @Autowired
    private RepositoryService repositoryService;

    @Value("${upload.bpmn.path}")
    private String uploadBpmnPath;

    /**
     * 添加流程定义，通过上传 bpmn
     * @param multipartFile  流程定文件
     * @param deployName    部署名称
     * @return
     */
    @PostMapping("/uploadStreamAndDeployment")
    public AjaxResponse uploadStreamAndDeployment(
            @RequestParam("bpmnFile")MultipartFile multipartFile,
            @RequestParam("deployName")String deployName
            ){
        try {
            // 获取上传的文件名
            String fileName = multipartFile.getOriginalFilename();
            // 获取文件名扩展名
            String extension = FilenameUtils.getExtension(fileName);
            // 获取文件字节流对象
            InputStream fileInputStream = multipartFile.getInputStream();

            Deployment deployment = null;
            // 如果后缀名 zip 压缩包
            if(extension.equals("zip")){
                // 流程部署
                ZipInputStream zip = new ZipInputStream(fileInputStream);
                deployment = repositoryService.createDeployment()
                        .addZipInputStream(zip)
                        .deploy();
            }else {
                // bpmn 文件部署
                deployment = repositoryService.createDeployment()
                        .addInputStream(fileName,fileInputStream)
                        .name(deployName)
                        .deploy();
            }
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),
                    deployment.getId()+"|"+fileName
            );
        }catch (Exception e){
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.ERROR.getCode(),
                    "添加流程定义失败",
                    e.getMessage()
            );
        }
    }

    /**
     * 添加流程定义通过在线提交 BPMN 的 xml
     * @param xmlBPMN
     * @param deployName
     * @return
     */
    @PostMapping("/addDeploymentByString")
    public AjaxResponse addDeploymentByString(@RequestParam("xmlBPMN")String xmlBPMN
                                               // , @RequestParam("deployName")String deployName // 这里的部署名称一般是前端 bpmn 中填写的
    ){
        try{
            Deployment deployment = repositoryService.createDeployment()
                    // 这里由于是 xml 的字符串没有资源名称，这里我们先写死一个
                    .addString("createWithBPMNJS.bpmn", xmlBPMN)
                   // .name(deployName) // 这里的部署名称一般是前端 bpmn 中填写的
                    .deploy();
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),
                    deployment.getId()
            );
        }catch (Exception e){
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.ERROR.getCode(),
                    "添加流程定义失败",
                    e.getMessage()
            );
        }
    }

    // 获取流程定义列表
    @GetMapping("/getDefinitions")
    public AjaxResponse getDefinitions(){
        try{
            // 保存流程定义列表
            List<Map<String,Object>> listMap = new ArrayList<Map<String,Object>>();
            // 查询流程列表
            List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery()
                    .list();
            list.forEach(pd -> {
                Map<String,Object> map = new HashMap<String,Object>();
                // 流程定义名称
                map.put("name",pd.getName());
                // 流程定义 key
                map.put("key",pd.getKey());
                // 资源名称
                map.put("resourceName",pd.getResourceName());
                // 部署 id
                map.put("deploymentId",pd.getDeploymentId());
                // 版本
                map.put("version",pd.getVersion());
                listMap.add(map);
            });
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),
                    listMap
            );
        }catch (Exception e){
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.ERROR.getCode(),
                    "获取流程定义列表失败",
                    e.toString()
            );
        }
    }
    
    // 获取流程定义的 xml 
    // 这个方法的作用是，在流程定的列表中当你想查看上传的 BPMN 是什么样子的时候，在 Activiti 7 以前，
    // 你只能在上传 BPMN 的同时在上传一张图片，这里去查看图片，那么在 activiti 7 中就不需要这么麻烦了，
    // 我们只需要将数据库中的二进制文件读出来并返回给前端 xml 即可
    /**
     * 
     * @param response
     * @param deploymentId    流程部署 id
     * @param resourceName    资源名称
     */
    @GetMapping("/getDefinitionXML")
    public void getDefinitionXML(HttpServletResponse response,
                                 @RequestParam("deploymentId")String deploymentId,
                                 @RequestParam("resourceName")String resourceName){
        // 声明返回的是一个 xml 类型
        response.setContentType("text/xml");
        try(
                // 读取流程部署文件流
                InputStream inputStream = repositoryService.getResourceAsStream(deploymentId, resourceName);
                BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
                BufferedOutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
                ) {
            byte[] bytes = new byte[1024];
            int len = 0;
            while ((len = bufferedInputStream.read(bytes)) != -1){
                outputStream.write(bytes, 0, len);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 获取流程部署列表
     * @return
     */
    @GetMapping("/getDeployments")
    public AjaxResponse getDeployments(){
        try {
            List<Map<String,Object>> listMap = new ArrayList<Map<String,Object>>();
            // 流程部署列表
            List<Deployment> list = repositoryService.createDeploymentQuery()
                    .list();
            list.forEach(p -> {
                Map<String,Object> map = new HashMap<String,Object>();
                // 部署id
                map.put("id", p.getId());
                // 部署名称
                map.put("name",p.getName());
                // 部署时间
                map.put("deploymentTime", p.getDeploymentTime());
                listMap.add(map);
            });
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),
                    listMap
            );
        }catch (Exception e){
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.ERROR.getCode(),
                    "获取流程部署列表失败",
                    e.toString()
            );
        }
    }

    /**
     * 删除流程定义
     * @param depId
     * @return
     */
    @DeleteMapping("/delDefinition")
    public AjaxResponse delDefinition(@RequestParam("depId")String depId){
        try {
            repositoryService.deleteDeployment(depId, true);
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),
                    null
            );
        }catch (Exception e){
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.ERROR.getCode(),
                    "删除流程定义失败",
                    e.toString()
            );
        }
    }

    /**
     * 添加流程定义通过在线提交 BPMN 的 xml
     * @param processFile
     * @return
     */
    @PostMapping("/uploadBPMN")
    public AjaxResponse addDeploymentByString(HttpServletRequest request,
                                              @RequestParam("processFile") MultipartFile processFile
                                              ){
        try{
            if(processFile.isEmpty()){
                return AjaxResponse.AjaxData(
                        GlobalConfig.ResponseCode.ERROR.getCode(),
                        GlobalConfig.ResponseCode.ERROR.getDesc(),
                        "BPMN 不能为空"
                );
            }
            // 获取原始文件名
            String originFileName = processFile.getOriginalFilename();
            // 获取文件后缀
            String suffixName = originFileName.substring(originFileName.lastIndexOf("."));
            // 新的文件名
            String fileName = UUID.randomUUID() + suffixName;
            // 上传文件的路径
            File filePath = new File(uploadBpmnPath+fileName);
            if(!filePath.getParentFile().exists()){
                filePath.getParentFile().mkdirs();
            }
            // 上传
            processFile.transferTo(filePath);
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.SUCCESS.getCode(),
                    GlobalConfig.ResponseCode.SUCCESS.getDesc(),
                    fileName
            );
        }catch (Exception e){
            return AjaxResponse.AjaxData(
                    GlobalConfig.ResponseCode.ERROR.getCode(),
                    "上传 BPMN 失败",
                    e.getMessage()
            );
        }
    }
}
